﻿#include "system_wrappers/source/thread_win.h"

#include <assert.h>
#include <process.h>
#include <stdio.h>
#include <windows.h>

#include "system_wrappers/interface/trace.h"
#include "modules/LogModule/log_module.h"
#include "system_wrappers/source/set_thread_name_win.h"

namespace gn
{

	ThreadWindows::ThreadWindows(ThreadRunFunction func, ThreadObj obj,
		ThreadPriority prio, const char* thread_name)
		: ThreadWrapper(),
		run_function_(func),
        after_function(NULL),
		obj_(obj),
		alive_(false),
		dead_(true),
		do_not_close_handle_(false),
		prio_(prio),
		event_(NULL),
		thread_(NULL),
		id_(0),
		name_(),
		set_thread_name_(false)
	{
		event_ = EventWrapper::Create();
		critsect_stop_ = CriticalSectionWrapper::CreateCriticalSection();
		critsect_interface_ = CriticalSectionWrapper::CreateCriticalSection();
		if (thread_name != NULL)
		{
			// Set the thread name to appear in the VS debugger.
			set_thread_name_ = true;
#ifdef _WIN32
#pragma warning(disable: 4996)
#endif // _WIN32

			strncpy(name_, thread_name, kThreadMaxNameLength);
#ifdef _WIN32
#pragma warning(default: 4996)
#endif // _WIN32
		}
	}

	ThreadWindows::~ThreadWindows()
	{
#ifdef _DEBUG
		assert(!alive_);
#endif
		if (thread_)
		{
			CloseHandle(thread_);
		}
		if (event_)
		{
			delete event_;
		}
		if (critsect_stop_)
		{
			delete critsect_stop_;
		}
		if (critsect_interface_)
		{
			delete critsect_interface_;
		}
	}

	uint32_t ThreadWrapper::GetThreadId()
	{
		return GetCurrentThreadId();
	}

	unsigned int WINAPI ThreadWindows::StartThread(LPVOID lp_parameter)
	{
		static_cast<ThreadWindows*>(lp_parameter)->Run();
		return 0;
	}

	bool ThreadWindows::Start(unsigned int& thread_id)
	{
		CriticalSectionScoped cs(critsect_interface_);
		if (!run_function_)
		{
			return false;
		}
		do_not_close_handle_ = false;

		// Set stack size to 1M
		thread_ = (HANDLE)_beginthreadex(NULL, 1024 * 1024, StartThread, (void*)this,
			0, &thread_id);
		if (thread_ == NULL)
		{
			return false;
		}
		id_ = thread_id;

		// Wait up to 10 seconds for the OS to call the callback function. Prevents
		// race condition if Stop() is called too quickly after start.
		if (kEventSignaled != event_->Wait(CCORE_EVENT_10_SEC)) {
			GN_TRACE1(kTraceError, kTraceUtility, -1,
				"win thread event never triggered");
			// Timed out. Something went wrong.
			return true;
		}

		if (!alive_)
		{
			return true;
		}

		switch (prio_)
		{
		case kLowPriority:
			SetThreadPriority(thread_, THREAD_PRIORITY_BELOW_NORMAL);
			break;
		case kNormalPriority:
			SetThreadPriority(thread_, THREAD_PRIORITY_NORMAL);
			break;
		case kHighPriority:
			SetThreadPriority(thread_, THREAD_PRIORITY_ABOVE_NORMAL);
			break;
		case kHighestPriority:
			SetThreadPriority(thread_, THREAD_PRIORITY_HIGHEST);
			break;
		case kRealtimePriority:
			SetThreadPriority(thread_, THREAD_PRIORITY_TIME_CRITICAL);
			break;
		};
		return true;
	}

	bool ThreadWindows::SetAffinity(const int* processor_numbers,
		const unsigned int amount_of_processors)
	{
		CriticalSectionScoped cs(critsect_interface_);
		DWORD_PTR processor_bit_mask = 0;
		for (unsigned int processor_index = 0;
			processor_index < amount_of_processors;
			++processor_index)
		{
			// Convert from an array with processor numbers to a bitmask
			// Processor numbers start at zero.
			// TODO(hellner): this looks like a bug. Shouldn't the '=' be a '+='?
			// Or even better |=
			processor_bit_mask = 1 << processor_numbers[processor_index];
		}
		return SetThreadAffinityMask(thread_, processor_bit_mask) != 0;
	}

	void ThreadWindows::SetNotAlive()
	{
		CriticalSectionScoped cs(critsect_interface_);
		alive_ = false;
	}

	bool ThreadWindows::Stop()
	{
		CriticalSectionScoped cs(critsect_interface_);
		critsect_stop_->Enter();

		// Prevents the handle from being closed in ThreadWindows::Run()
		do_not_close_handle_ = true;
		alive_ = false;
		event_->Set();
		bool signaled = false;
		if (thread_ && !dead_)
		{
			critsect_stop_->Leave();

			// Wait up to 2 seconds for the thread to complete.
			if (WAIT_OBJECT_0 == WaitForSingleObject(thread_, 2000))
			{
				signaled = true;
			}
			critsect_stop_->Enter();
		}
		if (thread_)
		{
			CloseHandle(thread_);
			thread_ = NULL;
		}
		critsect_stop_->Leave();

		if (dead_ || signaled)
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	bool ThreadWindows::Stop(bool& force)
	{
		CriticalSectionScoped cs(critsect_interface_);
		critsect_stop_->Enter();

		// Prevents the handle from being closed in ThreadWindows::Run()
		do_not_close_handle_ = true;
		alive_ = false;
		event_->Set();
		bool signaled = false;
		if (thread_ && !dead_)
		{
			critsect_stop_->Leave();

			// Wait up to 2 seconds for the thread to complete.
			if (WAIT_OBJECT_0 == WaitForSingleObject(thread_, 2000))
			{
				signaled = true;
			}
			critsect_stop_->Enter();
		}
		if (!dead_ || !signaled)
		{
			force = (TerminateThread(thread_,0) != 0);
		}
		if (thread_)
		{
			CloseHandle(thread_);
			thread_ = NULL;
		}
		critsect_stop_->Leave();

		if (dead_ || signaled)
		{
			return true;
		}
		else
		{
			return false;
		}
	}

    void ThreadWindows::SetThreadAfterFunc(ThreadAfterFunction afterFunc)
    {
        after_function = afterFunc;
    }

	void ThreadWindows::Run()
	{
		alive_ = true;
		dead_ = false;
		event_->Set();

		// All tracing must be after event_->Set to avoid deadlock in Trace.
		if (set_thread_name_)
		{
			GN_TRACE(kTraceStateInfo, kTraceUtility, id_,
				"Thread with name:%s started ", name_);
			SetThreadName(static_cast<DWORD>(-1), name_); // -1, set thread name for the calling thread.
		}
		else
		{
			GN_TRACE(kTraceStateInfo, kTraceUtility, id_,
				"Thread without name started");
		}

		do
		{
			if (run_function_)
			{
				if (!run_function_(obj_))
				{
					alive_ = false;
				}
			}
			else
			{
				alive_ = false;
			}
		} while (alive_);

		if (set_thread_name_)
		{
			GN_TRACE(kTraceStateInfo, kTraceUtility, id_,
				"Thread with name:%s stopped", name_);
		}
		else
		{
			GN_TRACE(kTraceStateInfo, kTraceUtility, id_,
				"Thread without name stopped");
		}

		critsect_stop_->Enter();

		if (thread_ && !do_not_close_handle_)
		{
			HANDLE thread = thread_;
			thread_ = NULL;
			CloseHandle(thread);
		}
		dead_ = true;

		critsect_stop_->Leave();

        if (after_function)
        {
            after_function(obj_);
        }
	};

}  // namespace gn
